/*
 * EAPOL supplicant state machines
 * Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include "includes.h"

#include "common.h"
#include "eapol_supp_sm.h"
#include "eap_peer/eap.h"
#include "eap_peer/eap_i.h"
#include "eloop.h"
#include "eapol_common.h"
#include "md5.h"
#include "rc4.h"
#include "state_machine.h"
#include "wpabuf.h"

#define STATE_MACHINE_DATA struct eapol_sm
#define STATE_MACHINE_DEBUG_PREFIX "EAPOL"


/* IEEE 802.1X-2004 - Supplicant - EAPOL state machines */

/**
 * struct eapol_sm - Internal data for EAPOL state machines
 */
struct eapol_sm {
        /* Timers */
        unsigned int authWhile;
        unsigned int heldWhile;
        unsigned int startWhen;
        unsigned int idleWhile; /* for EAP state machine */
        int timer_tick_enabled;

        /* Global variables */
        Boolean eapFail;
        Boolean eapolEap;
        Boolean eapSuccess;
        Boolean initialize;
        Boolean keyDone;
        Boolean keyRun;
        PortControl portControl;
        Boolean portEnabled;
        PortStatus suppPortStatus;  /* dot1xSuppControlledPortStatus */
        Boolean portValid;
        Boolean suppAbort;
        Boolean suppFail;
        Boolean suppStart;
        Boolean suppSuccess;
        Boolean suppTimeout;

        Boolean eapFailure;
        /* Supplicant PAE state machine */
        enum {
                SUPP_PAE_UNKNOWN = 0,
                SUPP_PAE_DISCONNECTED = 1,
                SUPP_PAE_LOGOFF = 2,
                SUPP_PAE_CONNECTING = 3,
                SUPP_PAE_AUTHENTICATING = 4,
                SUPP_PAE_AUTHENTICATED = 5,
                /* unused(6) */
                SUPP_PAE_HELD = 7,
                SUPP_PAE_RESTART = 8,
                SUPP_PAE_S_FORCE_AUTH = 9,
                SUPP_PAE_S_FORCE_UNAUTH = 10
        } SUPP_PAE_state; /* dot1xSuppPaeState */
        /* Variables */
        Boolean userLogoff;
        Boolean logoffSent;
        unsigned int startCount;
        Boolean eapRestart;
        PortControl sPortMode;
        /* Constants */
        unsigned int heldPeriod; /* dot1xSuppHeldPeriod */
        unsigned int startPeriod; /* dot1xSuppStartPeriod */
        unsigned int maxStart; /* dot1xSuppMaxStart */

        /* Key Receive state machine */
        enum {
                KEY_RX_UNKNOWN = 0,
                KEY_RX_NO_KEY_RECEIVE, KEY_RX_KEY_RECEIVE
        } KEY_RX_state;
        /* Variables */
        Boolean rxKey;

        /* Supplicant Backend state machine */
        enum {
                SUPP_BE_UNKNOWN = 0,
                SUPP_BE_INITIALIZE = 1,
                SUPP_BE_IDLE = 2,
                SUPP_BE_REQUEST = 3,
                SUPP_BE_RECEIVE = 4,
                SUPP_BE_RESPONSE = 5,
                SUPP_BE_FAIL = 6,
                SUPP_BE_TIMEOUT = 7, 
                SUPP_BE_SUCCESS = 8
        } SUPP_BE_state; /* dot1xSuppBackendPaeState */
        /* Variables */
        Boolean eapNoResp;
        Boolean eapReq;
        Boolean eapResp;
        /* Constants */
        unsigned int authPeriod; /* dot1xSuppAuthPeriod */

        /* Statistics */
        unsigned int dot1xSuppEapolFramesRx;
        unsigned int dot1xSuppEapolFramesTx;
        unsigned int dot1xSuppEapolStartFramesTx;
        unsigned int dot1xSuppEapolLogoffFramesTx;
        unsigned int dot1xSuppEapolRespFramesTx;
        unsigned int dot1xSuppEapolReqIdFramesRx;
        unsigned int dot1xSuppEapolReqFramesRx;
        unsigned int dot1xSuppInvalidEapolFramesRx;
        unsigned int dot1xSuppEapLengthErrorFramesRx;
        unsigned int dot1xSuppLastEapolFrameVersion;
        unsigned char dot1xSuppLastEapolFrameSource[6];

        /* Miscellaneous variables (not defined in IEEE 802.1X-2004) */
        Boolean changed;
        struct eap_sm *eap;
        struct eap_peer_config *config;
        Boolean initial_req;
        u8 *last_rx_key;
        size_t last_rx_key_len;
        struct wpabuf *eapReqData; /* for EAP */
        Boolean altAccept; /* for EAP */
        Boolean altReject; /* for EAP */
        Boolean replay_counter_valid;
        u8 last_replay_counter[16];
        struct eapol_config conf;
        struct eapol_ctx *ctx;
        enum { EAPOL_CB_IN_PROGRESS = 0, EAPOL_CB_SUCCESS, EAPOL_CB_FAILURE }
                cb_status;
        Boolean cached_pmk;

        Boolean unicast_key_received, broadcast_key_received;
};


#define IEEE8021X_REPLAY_COUNTER_LEN 8
#define IEEE8021X_KEY_SIGN_LEN 16
#define IEEE8021X_KEY_IV_LEN 16

#define IEEE8021X_KEY_INDEX_FLAG 0x80
#define IEEE8021X_KEY_INDEX_MASK 0x03

#ifdef _MSC_VER
#pragma pack(push, 1)
#endif /* _MSC_VER */

struct ieee802_1x_eapol_key {
        u8 type;
        /* Note: key_length is unaligned */
        u8 key_length[2];
        /* does not repeat within the life of the keying material used to
         * encrypt the Key field; 64-bit NTP timestamp MAY be used here */
        u8 replay_counter[IEEE8021X_REPLAY_COUNTER_LEN];
        u8 key_iv[IEEE8021X_KEY_IV_LEN]; /* cryptographically random number */
        u8 key_index; /* key flag in the most significant bit:
                       * 0 = broadcast (default key),
                       * 1 = unicast (key mapping key); key index is in the
                       * 7 least significant bits */
        /* HMAC-MD5 message integrity check computed with MS-MPPE-Send-Key as
         * the key */
        u8 key_signature[IEEE8021X_KEY_SIGN_LEN];

        /* followed by key: if packet body length = 44 + key length, then the
         * key field (of key_length bytes) contains the key in encrypted form;
         * if packet body length = 44, key field is absent and key_length
         * represents the number of least significant octets from
         * MS-MPPE-Send-Key attribute to be used as the keying material;
         * RC4 key used in encryption = Key-IV + MS-MPPE-Recv-Key */
} STRUCT_PACKED;

#ifdef _MSC_VER
#pragma pack(pop)
#endif /* _MSC_VER */


static void eapol_sm_txLogoff(struct eapol_sm *sm);
static void eapol_sm_txStart(struct eapol_sm *sm);
static void eapol_sm_processKey(struct eapol_sm *sm);
static void eapol_sm_getSuppRsp(struct eapol_sm *sm);
static void eapol_sm_txSuppRsp(struct eapol_sm *sm);
static void eapol_sm_abortSupp(struct eapol_sm *sm);
static void eapol_sm_abort_cached(struct eapol_sm *sm);
static void eapol_sm_step_timeout(void *eloop_ctx, void *timeout_ctx);


/* Port Timers state machine - implemented as a function that will be called
 * once a second as a registered event loop timeout */
static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx)
{
        struct eapol_sm *sm = timeout_ctx;

        if (sm->authWhile > 0) {
                sm->authWhile--;
                if (sm->authWhile == 0)
                        wpa_printf(MSG_DEBUG, "EAPOL: authWhile --> 0");
        }
        if (sm->heldWhile > 0) {
                sm->heldWhile--;
                if (sm->heldWhile == 0)
                        wpa_printf(MSG_DEBUG, "EAPOL: heldWhile --> 0");
        }
        if (sm->startWhen > 0) {
                sm->startWhen--;
                if (sm->startWhen == 0)
                        wpa_printf(MSG_DEBUG, "EAPOL: startWhen --> 0");
        }
        if (sm->idleWhile > 0) {
                sm->idleWhile--;
                if (sm->idleWhile == 0)
                        wpa_printf(MSG_DEBUG, "EAPOL: idleWhile --> 0");
        }

        if (sm->authWhile | sm->heldWhile | sm->startWhen | sm->idleWhile) {
                eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx,
                                       sm);
        } else {
                wpa_printf(MSG_DEBUG, "EAPOL: disable timer tick");
                sm->timer_tick_enabled = 0;
        }
        eapol_sm_step(sm);
}


static void eapol_enable_timer_tick(struct eapol_sm *sm)
{
        if (sm->timer_tick_enabled)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: enable timer tick");
        sm->timer_tick_enabled = 1;
        eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm);
        eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
}


SM_STATE(SUPP_PAE, LOGOFF)
{
        SM_ENTRY(SUPP_PAE, LOGOFF);
        eapol_sm_txLogoff(sm);
        sm->logoffSent = TRUE;
        sm->suppPortStatus = Unauthorized;
}


SM_STATE(SUPP_PAE, DISCONNECTED)
{
        SM_ENTRY(SUPP_PAE, DISCONNECTED);
        sm->sPortMode = Auto;
        sm->startCount = 0;
        sm->logoffSent = FALSE;
        sm->suppPortStatus = Unauthorized;
        sm->suppAbort = TRUE;

        sm->unicast_key_received = FALSE;
        sm->broadcast_key_received = FALSE;
}


SM_STATE(SUPP_PAE, CONNECTING)
{
        int send_start = sm->SUPP_PAE_state == SUPP_PAE_CONNECTING;
        SM_ENTRY(SUPP_PAE, CONNECTING);
        if (send_start) {
                sm->startWhen = sm->startPeriod;
                sm->startCount++;
        } else {
                /*
                 * Do not send EAPOL-Start immediately since in most cases,
                 * Authenticator is going to start authentication immediately
                 * after association and an extra EAPOL-Start is just going to
                 * delay authentication. Use a short timeout to send the first
                 * EAPOL-Start if Authenticator does not start authentication.
                 */
#ifdef CONFIG_WPS
                /* Reduce latency on starting WPS negotiation. */
                sm->startWhen = 1;
#else /* CONFIG_WPS */
                sm->startWhen = 3;
#endif /* CONFIG_WPS */
        }
        eapol_enable_timer_tick(sm);
        sm->eapolEap = FALSE;
        if (send_start)
                eapol_sm_txStart(sm);
}


SM_STATE(SUPP_PAE, AUTHENTICATING)
{
        SM_ENTRY(SUPP_PAE, AUTHENTICATING);
        sm->startCount = 0;
        sm->suppSuccess = FALSE;
        sm->suppFail = FALSE;
        sm->suppTimeout = FALSE;
        sm->keyRun = FALSE;
        sm->keyDone = FALSE;
        sm->suppStart = TRUE;
}


SM_STATE(SUPP_PAE, HELD)
{
        SM_ENTRY(SUPP_PAE, HELD);
        sm->heldWhile = sm->heldPeriod;
        eapol_enable_timer_tick(sm);
        sm->suppPortStatus = Unauthorized;
        sm->cb_status = EAPOL_CB_FAILURE;
}


SM_STATE(SUPP_PAE, AUTHENTICATED)
{
        SM_ENTRY(SUPP_PAE, AUTHENTICATED);
        sm->suppPortStatus = Authorized;
        sm->cb_status = EAPOL_CB_SUCCESS;
}


SM_STATE(SUPP_PAE, RESTART)
{
        SM_ENTRY(SUPP_PAE, RESTART);
        sm->eapRestart = TRUE;
}


SM_STATE(SUPP_PAE, S_FORCE_AUTH)
{
        SM_ENTRY(SUPP_PAE, S_FORCE_AUTH);
        sm->suppPortStatus = Authorized;
        sm->sPortMode = ForceAuthorized;
}


SM_STATE(SUPP_PAE, S_FORCE_UNAUTH)
{
        SM_ENTRY(SUPP_PAE, S_FORCE_UNAUTH);
        sm->suppPortStatus = Unauthorized;
        sm->sPortMode = ForceUnauthorized;
        eapol_sm_txLogoff(sm);
}


SM_STEP(SUPP_PAE)
{
        if ((sm->userLogoff && !sm->logoffSent) &&
            !(sm->initialize || !sm->portEnabled))
                SM_ENTER_GLOBAL(SUPP_PAE, LOGOFF);
        else if (((sm->portControl == Auto) &&
                  (sm->sPortMode != sm->portControl)) ||
                 sm->initialize || !sm->portEnabled)
                SM_ENTER_GLOBAL(SUPP_PAE, DISCONNECTED);
        else if ((sm->portControl == ForceAuthorized) &&
                 (sm->sPortMode != sm->portControl) &&
                 !(sm->initialize || !sm->portEnabled))
                SM_ENTER_GLOBAL(SUPP_PAE, S_FORCE_AUTH);
        else if ((sm->portControl == ForceUnauthorized) &&
                 (sm->sPortMode != sm->portControl) &&
                 !(sm->initialize || !sm->portEnabled))
                SM_ENTER_GLOBAL(SUPP_PAE, S_FORCE_UNAUTH);
        else switch (sm->SUPP_PAE_state) {
        case SUPP_PAE_UNKNOWN:
                break;
        case SUPP_PAE_LOGOFF:
                if (!sm->userLogoff)
                        SM_ENTER(SUPP_PAE, DISCONNECTED);
                break;
        case SUPP_PAE_DISCONNECTED:
                SM_ENTER(SUPP_PAE, CONNECTING);
                break;
        case SUPP_PAE_CONNECTING:
                if (sm->startWhen == 0 && sm->startCount < sm->maxStart)
                        SM_ENTER(SUPP_PAE, CONNECTING);
                else if (sm->startWhen == 0 &&
                         sm->startCount >= sm->maxStart &&
                         sm->portValid)
                        SM_ENTER(SUPP_PAE, AUTHENTICATED);
                else if (sm->eapSuccess || sm->eapFail)
                        SM_ENTER(SUPP_PAE, AUTHENTICATING);
                else if (sm->eapolEap)
                        SM_ENTER(SUPP_PAE, RESTART);
                else if (sm->startWhen == 0 &&
                         sm->startCount >= sm->maxStart &&
                         !sm->portValid)
                        SM_ENTER(SUPP_PAE, HELD);
                break;
        case SUPP_PAE_AUTHENTICATING:
                if (sm->eapSuccess && !sm->portValid &&
                    sm->conf.accept_802_1x_keys &&
                    sm->conf.required_keys == 0) {
                        wpa_printf(MSG_DEBUG, "EAPOL: IEEE 802.1X for "
                                   "plaintext connection; no EAPOL-Key frames "
                                   "required");
                        sm->portValid = TRUE;
                        if (sm->ctx->eapol_done_cb)
                                sm->ctx->eapol_done_cb(sm->ctx->ctx);
                }
                if (sm->eapSuccess && sm->portValid)
                        SM_ENTER(SUPP_PAE, AUTHENTICATED);
                else if (sm->eapFail || (sm->keyDone && !sm->portValid))
                        SM_ENTER(SUPP_PAE, HELD);
                else if (sm->suppTimeout)
                        SM_ENTER(SUPP_PAE, CONNECTING);
                break;
        case SUPP_PAE_HELD:
                if (sm->heldWhile == 0)
                        SM_ENTER(SUPP_PAE, CONNECTING);
                else if (sm->eapolEap)
                        SM_ENTER(SUPP_PAE, RESTART);
                break;
        case SUPP_PAE_AUTHENTICATED:
                if (sm->eapolEap && sm->portValid)
                        SM_ENTER(SUPP_PAE, RESTART);
                else if (!sm->portValid)
                        SM_ENTER(SUPP_PAE, DISCONNECTED);
                break;
        case SUPP_PAE_RESTART:
                if (!sm->eapRestart)
                        SM_ENTER(SUPP_PAE, AUTHENTICATING);
                break;
        case SUPP_PAE_S_FORCE_AUTH:
                break;
        case SUPP_PAE_S_FORCE_UNAUTH:
                break;
        }
}


SM_STATE(KEY_RX, NO_KEY_RECEIVE)
{
        SM_ENTRY(KEY_RX, NO_KEY_RECEIVE);
}


SM_STATE(KEY_RX, KEY_RECEIVE)
{
        SM_ENTRY(KEY_RX, KEY_RECEIVE);
        eapol_sm_processKey(sm);
        sm->rxKey = FALSE;
}


SM_STEP(KEY_RX)
{
        if (sm->initialize || !sm->portEnabled)
                SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE);
        switch (sm->KEY_RX_state) {
        case KEY_RX_UNKNOWN:
                break;
        case KEY_RX_NO_KEY_RECEIVE:
                if (sm->rxKey)
                        SM_ENTER(KEY_RX, KEY_RECEIVE);
                break;
        case KEY_RX_KEY_RECEIVE:
                if (sm->rxKey)
                        SM_ENTER(KEY_RX, KEY_RECEIVE);
                break;
        }
}


SM_STATE(SUPP_BE, REQUEST)
{
        SM_ENTRY(SUPP_BE, REQUEST);
        sm->authWhile = 0;
        sm->eapReq = TRUE;
        eapol_sm_getSuppRsp(sm);
}


SM_STATE(SUPP_BE, RESPONSE)
{
        SM_ENTRY(SUPP_BE, RESPONSE);
        eapol_sm_txSuppRsp(sm);
        sm->eapResp = FALSE;
}


SM_STATE(SUPP_BE, SUCCESS)
{
        SM_ENTRY(SUPP_BE, SUCCESS);
        sm->keyRun = TRUE;
        sm->suppSuccess = TRUE;

        if (eap_key_available(sm->eap)) {
                /* New key received - clear IEEE 802.1X EAPOL-Key replay
                 * counter */
                sm->replay_counter_valid = FALSE;
        }
}


SM_STATE(SUPP_BE, FAIL)
{
        SM_ENTRY(SUPP_BE, FAIL);
        sm->suppFail = TRUE;
}


SM_STATE(SUPP_BE, TIMEOUT)
{
        SM_ENTRY(SUPP_BE, TIMEOUT);
        sm->suppTimeout = TRUE;
}


SM_STATE(SUPP_BE, IDLE)
{
        SM_ENTRY(SUPP_BE, IDLE);
        sm->suppStart = FALSE;
        sm->initial_req = TRUE;
}


SM_STATE(SUPP_BE, INITIALIZE)
{
        SM_ENTRY(SUPP_BE, INITIALIZE);
        eapol_sm_abortSupp(sm);
        sm->suppAbort = FALSE;
}


SM_STATE(SUPP_BE, RECEIVE)
{
        SM_ENTRY(SUPP_BE, RECEIVE);
        sm->authWhile = sm->authPeriod;
        eapol_enable_timer_tick(sm);
        sm->eapolEap = FALSE;
        sm->eapNoResp = FALSE;
        sm->initial_req = FALSE;
}


SM_STEP(SUPP_BE)
{
        if (sm->initialize || sm->suppAbort)
                SM_ENTER_GLOBAL(SUPP_BE, INITIALIZE);
        else switch (sm->SUPP_BE_state) {
        case SUPP_BE_UNKNOWN:
                break;
        case SUPP_BE_REQUEST:
                /*
                 * IEEE Std 802.1X-2004 has transitions from REQUEST to FAIL
                 * and SUCCESS based on eapFail and eapSuccess, respectively.
                 * However, IEEE Std 802.1X-2004 is also specifying that
                 * eapNoResp should be set in conjuction with eapSuccess and
                 * eapFail which would mean that more than one of the
                 * transitions here would be activated at the same time.
                 * Skipping RESPONSE and/or RECEIVE states in these cases can
                 * cause problems and the direct transitions to do not seem
                 * correct. Because of this, the conditions for these
                 * transitions are verified only after eapNoResp. They are
                 * unlikely to be used since eapNoResp should always be set if
                 * either of eapSuccess or eapFail is set.
                 */
                if (sm->eapResp && sm->eapNoResp) {
                        wpa_printf(MSG_DEBUG, "EAPOL: SUPP_BE REQUEST: both "
                                   "eapResp and eapNoResp set?!");
                }
                if (sm->eapResp)
                        SM_ENTER(SUPP_BE, RESPONSE);
                else if (sm->eapNoResp)
                        SM_ENTER(SUPP_BE, RECEIVE);
                else if (sm->eapFail)
                        SM_ENTER(SUPP_BE, FAIL);
                else if (sm->eapSuccess)
                        SM_ENTER(SUPP_BE, SUCCESS);
                break;
        case SUPP_BE_RESPONSE:
                SM_ENTER(SUPP_BE, RECEIVE);
                break;
        case SUPP_BE_SUCCESS:
                SM_ENTER(SUPP_BE, IDLE);
                break;
        case SUPP_BE_FAIL:
                SM_ENTER(SUPP_BE, IDLE);
                break;
        case SUPP_BE_TIMEOUT:
                SM_ENTER(SUPP_BE, IDLE);
                break;
        case SUPP_BE_IDLE:
                if (sm->eapFail && sm->suppStart)
                        SM_ENTER(SUPP_BE, FAIL);
                else if (sm->eapolEap && sm->suppStart)
                        SM_ENTER(SUPP_BE, REQUEST);
                else if (sm->eapSuccess && sm->suppStart)
                        SM_ENTER(SUPP_BE, SUCCESS);
                break;
        case SUPP_BE_INITIALIZE:
                SM_ENTER(SUPP_BE, IDLE);
                break;
        case SUPP_BE_RECEIVE:
                if (sm->eapolEap)
                        SM_ENTER(SUPP_BE, REQUEST);
                else if (sm->eapFail)
                        SM_ENTER(SUPP_BE, FAIL);
                else if (sm->authWhile == 0)
                        SM_ENTER(SUPP_BE, TIMEOUT);
                else if (sm->eapSuccess)
                        SM_ENTER(SUPP_BE, SUCCESS);
                break;
        }
}


static void eapol_sm_txLogoff(struct eapol_sm *sm)
{
        wpa_printf(MSG_DEBUG, "EAPOL: txLogoff");
        sm->ctx->eapol_send(sm->ctx->eapol_send_ctx,
                            IEEE802_1X_TYPE_EAPOL_LOGOFF, (u8 *) "", 0);
        sm->dot1xSuppEapolLogoffFramesTx++;
        sm->dot1xSuppEapolFramesTx++;
}


static void eapol_sm_txStart(struct eapol_sm *sm)
{
        wpa_printf(MSG_DEBUG, "EAPOL: txStart");
        sm->ctx->eapol_send(sm->ctx->eapol_send_ctx,
                            IEEE802_1X_TYPE_EAPOL_START, (u8 *) "", 0);
        sm->dot1xSuppEapolStartFramesTx++;
        sm->dot1xSuppEapolFramesTx++;
}


#define IEEE8021X_ENCR_KEY_LEN 32
#define IEEE8021X_SIGN_KEY_LEN 32

struct eap_key_data {
        u8 encr_key[IEEE8021X_ENCR_KEY_LEN];
        u8 sign_key[IEEE8021X_SIGN_KEY_LEN];
};


static void eapol_sm_processKey(struct eapol_sm *sm)
{
        struct ieee802_1x_hdr *hdr;
        struct ieee802_1x_eapol_key *key;
        struct eap_key_data keydata;
        u8 orig_key_sign[IEEE8021X_KEY_SIGN_LEN], datakey[32];
        u8 ekey[IEEE8021X_KEY_IV_LEN + IEEE8021X_ENCR_KEY_LEN];
        int key_len, res, sign_key_len, encr_key_len;
        u16 rx_key_length;

        wpa_printf(MSG_DEBUG, "EAPOL: processKey");
        if (sm->last_rx_key == NULL)
                return;

        if (!sm->conf.accept_802_1x_keys) {
                wpa_printf(MSG_WARNING, "EAPOL: Received IEEE 802.1X EAPOL-Key"
                           " even though this was not accepted - "
                           "ignoring this packet");
                return;
        }

        hdr = (struct ieee802_1x_hdr *) sm->last_rx_key;
        key = (struct ieee802_1x_eapol_key *) (hdr + 1);
        if (sizeof(*hdr) + be_to_host16(hdr->length) > sm->last_rx_key_len) {
                wpa_printf(MSG_WARNING, "EAPOL: Too short EAPOL-Key frame");
                return;
        }
        rx_key_length = WPA_GET_BE16(key->key_length);
        wpa_printf(MSG_DEBUG, "EAPOL: RX IEEE 802.1X ver=%d type=%d len=%d "
                   "EAPOL-Key: type=%d key_length=%d key_index=0x%x",
                   hdr->version, hdr->type, be_to_host16(hdr->length),
                   key->type, rx_key_length, key->key_index);

        eapol_sm_notify_lower_layer_success(sm, 1);
        sign_key_len = IEEE8021X_SIGN_KEY_LEN;
        encr_key_len = IEEE8021X_ENCR_KEY_LEN;
        res = eapol_sm_get_key(sm, (u8 *) &keydata, sizeof(keydata));
        if (res < 0) {
                wpa_printf(MSG_DEBUG, "EAPOL: Could not get master key for "
                           "decrypting EAPOL-Key keys");
                return;
        }
        if (res == 16) {
                /* LEAP derives only 16 bytes of keying material. */
                res = eapol_sm_get_key(sm, (u8 *) &keydata, 16);
                if (res) {
                        wpa_printf(MSG_DEBUG, "EAPOL: Could not get LEAP "
                                   "master key for decrypting EAPOL-Key keys");
                        return;
                }
                sign_key_len = 16;
                encr_key_len = 16;
                os_memcpy(keydata.sign_key, keydata.encr_key, 16);
        } else if (res) {
                wpa_printf(MSG_DEBUG, "EAPOL: Could not get enough master key "
                           "data for decrypting EAPOL-Key keys (res=%d)", res);
                return;
        }

        /* The key replay_counter must increase when same master key */
        if (sm->replay_counter_valid &&
            os_memcmp(sm->last_replay_counter, key->replay_counter,
                      IEEE8021X_REPLAY_COUNTER_LEN) >= 0) {
                wpa_printf(MSG_WARNING, "EAPOL: EAPOL-Key replay counter did "
                           "not increase - ignoring key");
                wpa_hexdump(MSG_DEBUG, "EAPOL: last replay counter",
                            sm->last_replay_counter,
                            IEEE8021X_REPLAY_COUNTER_LEN);
                wpa_hexdump(MSG_DEBUG, "EAPOL: received replay counter",
                            key->replay_counter, IEEE8021X_REPLAY_COUNTER_LEN);
                return;
        }

        /* Verify key signature (HMAC-MD5) */
        os_memcpy(orig_key_sign, key->key_signature, IEEE8021X_KEY_SIGN_LEN);
        os_memset(key->key_signature, 0, IEEE8021X_KEY_SIGN_LEN);
        hmac_md5(keydata.sign_key, sign_key_len,
                 sm->last_rx_key, sizeof(*hdr) + be_to_host16(hdr->length),
                 key->key_signature);
        if (os_memcmp(orig_key_sign, key->key_signature,
                      IEEE8021X_KEY_SIGN_LEN) != 0) {
                wpa_printf(MSG_DEBUG, "EAPOL: Invalid key signature in "
                           "EAPOL-Key packet");
                os_memcpy(key->key_signature, orig_key_sign,
                          IEEE8021X_KEY_SIGN_LEN);
                return;
        }
        wpa_printf(MSG_DEBUG, "EAPOL: EAPOL-Key key signature verified");

        key_len = be_to_host16(hdr->length) - sizeof(*key);
        if (key_len > 32 || rx_key_length > 32) {
                wpa_printf(MSG_WARNING, "EAPOL: Too long key data length %d",
                           key_len ? key_len : rx_key_length);
                return;
        }
        if (key_len == rx_key_length) {
                os_memcpy(ekey, key->key_iv, IEEE8021X_KEY_IV_LEN);
                os_memcpy(ekey + IEEE8021X_KEY_IV_LEN, keydata.encr_key,
                          encr_key_len);
                os_memcpy(datakey, key + 1, key_len);
                rc4_skip(ekey, IEEE8021X_KEY_IV_LEN + encr_key_len, 0,
                         datakey, key_len);
                wpa_hexdump_key(MSG_DEBUG, "EAPOL: Decrypted(RC4) key",
                                datakey, key_len);
        } else if (key_len == 0) {
                /*
                 * IEEE 802.1X-2004 specifies that least significant Key Length
                 * octets from MS-MPPE-Send-Key are used as the key if the key
                 * data is not present. This seems to be meaning the beginning
                 * of the MS-MPPE-Send-Key. In addition, MS-MPPE-Send-Key in
                 * Supplicant corresponds to MS-MPPE-Recv-Key in Authenticator.
                 * Anyway, taking the beginning of the keying material from EAP
                 * seems to interoperate with Authenticators.
                 */
                key_len = rx_key_length;
                os_memcpy(datakey, keydata.encr_key, key_len);
                wpa_hexdump_key(MSG_DEBUG, "EAPOL: using part of EAP keying "
                                "material data encryption key",
                                datakey, key_len);
        } else {
                wpa_printf(MSG_DEBUG, "EAPOL: Invalid key data length %d "
                           "(key_length=%d)", key_len, rx_key_length);
                return;
        }

        sm->replay_counter_valid = TRUE;
        os_memcpy(sm->last_replay_counter, key->replay_counter,
                  IEEE8021X_REPLAY_COUNTER_LEN);

        wpa_printf(MSG_DEBUG, "EAPOL: Setting dynamic WEP key: %s keyidx %d "
                   "len %d",
                   key->key_index & IEEE8021X_KEY_INDEX_FLAG ?
                   "unicast" : "broadcast",
                   key->key_index & IEEE8021X_KEY_INDEX_MASK, key_len);

        if (sm->ctx->set_wep_key &&
            sm->ctx->set_wep_key(sm->ctx->ctx,
                                 key->key_index & IEEE8021X_KEY_INDEX_FLAG,
                                 key->key_index & IEEE8021X_KEY_INDEX_MASK,
                                 datakey, key_len) < 0) {
                wpa_printf(MSG_WARNING, "EAPOL: Failed to set WEP key to the "
                           " driver.");
        } else {
                if (key->key_index & IEEE8021X_KEY_INDEX_FLAG)
                        sm->unicast_key_received = TRUE;
                else
                        sm->broadcast_key_received = TRUE;

                if ((sm->unicast_key_received ||
                     !(sm->conf.required_keys & EAPOL_REQUIRE_KEY_UNICAST)) &&
                    (sm->broadcast_key_received ||
                     !(sm->conf.required_keys & EAPOL_REQUIRE_KEY_BROADCAST)))
                {
                        wpa_printf(MSG_DEBUG, "EAPOL: all required EAPOL-Key "
                                   "frames received");
                        sm->portValid = TRUE;
                        if (sm->ctx->eapol_done_cb)
                                sm->ctx->eapol_done_cb(sm->ctx->ctx);
                }
        }
}


static void eapol_sm_getSuppRsp(struct eapol_sm *sm)
{
        wpa_printf(MSG_DEBUG, "EAPOL: getSuppRsp");
        /* EAP layer processing; no special code is needed, since Supplicant
         * Backend state machine is waiting for eapNoResp or eapResp to be set
         * and these are only set in the EAP state machine when the processing
         * has finished. */
}


static void eapol_sm_txSuppRsp(struct eapol_sm *sm)
{
        struct wpabuf *resp;

        wpa_printf(MSG_DEBUG, "EAPOL: txSuppRsp");
        resp = eap_get_eapRespData(sm->eap);
        if (resp == NULL) {
                wpa_printf(MSG_WARNING, "EAPOL: txSuppRsp - EAP response data "
                           "not available");
                return;
        }

        /* Send EAP-Packet from the EAP layer to the Authenticator */
        sm->ctx->eapol_send(sm->ctx->eapol_send_ctx,
                            IEEE802_1X_TYPE_EAP_PACKET, wpabuf_head(resp),
                            wpabuf_len(resp));

        /* eapRespData is not used anymore, so free it here */
        wpabuf_free(resp);

        if (sm->initial_req)
                sm->dot1xSuppEapolReqIdFramesRx++;
        else
                sm->dot1xSuppEapolReqFramesRx++;
        sm->dot1xSuppEapolRespFramesTx++;
        sm->dot1xSuppEapolFramesTx++;
}


static void eapol_sm_abortSupp(struct eapol_sm *sm)
{
        /* release system resources that may have been allocated for the
         * authentication session */
        os_free(sm->last_rx_key);
        sm->last_rx_key = NULL;
        wpabuf_free(sm->eapReqData);
        sm->eapReqData = NULL;
        eap_sm_abort(sm->eap);
}


static void eapol_sm_step_timeout(void *eloop_ctx, void *timeout_ctx)
{
        eapol_sm_step(timeout_ctx);
}


/**
 * eapol_sm_step - EAPOL state machine step function
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * This function is called to notify the state machine about changed external
 * variables. It will step through the EAPOL state machines in loop to process
 * all triggered state changes.
 */
void eapol_sm_step(struct eapol_sm *sm)
{
        int i;

        /* In theory, it should be ok to run this in loop until !changed.
         * However, it is better to use a limit on number of iterations to
         * allow events (e.g., SIGTERM) to stop the program cleanly if the
         * state machine were to generate a busy loop. */
        for (i = 0; i < 100; i++) {
                sm->changed = FALSE;
                SM_STEP_RUN(SUPP_PAE);
                SM_STEP_RUN(KEY_RX);
                SM_STEP_RUN(SUPP_BE);
                if (eap_peer_sm_step(sm->eap))
                        sm->changed = TRUE;
                if (!sm->changed)
                        break;
        }

        if (sm->changed) {
                /* restart EAPOL state machine step from timeout call in order
                 * to allow other events to be processed. */
                eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
                eloop_register_timeout(0, 0, eapol_sm_step_timeout, NULL, sm);
        }

        if (sm->ctx->cb && sm->cb_status != EAPOL_CB_IN_PROGRESS) {
                int success = sm->cb_status == EAPOL_CB_SUCCESS ? 1 : 0;
                sm->cb_status = EAPOL_CB_IN_PROGRESS;
                sm->ctx->cb(sm, success, sm->ctx->cb_ctx);
        }
}


#ifdef CONFIG_CTRL_IFACE
static const char *eapol_supp_pae_state(int state)
{
        switch (state) {
        case SUPP_PAE_LOGOFF:
                return "LOGOFF";
        case SUPP_PAE_DISCONNECTED:
                return "DISCONNECTED";
        case SUPP_PAE_CONNECTING:
                return "CONNECTING";
        case SUPP_PAE_AUTHENTICATING:
                return "AUTHENTICATING";
        case SUPP_PAE_HELD:
                return "HELD";
        case SUPP_PAE_AUTHENTICATED:
                return "AUTHENTICATED";
        case SUPP_PAE_RESTART:
                return "RESTART";
        default:
                return "UNKNOWN";
        }
}


static const char *eapol_supp_be_state(int state)
{
        switch (state) {
        case SUPP_BE_REQUEST:
                return "REQUEST";
        case SUPP_BE_RESPONSE:
                return "RESPONSE";
        case SUPP_BE_SUCCESS:
                return "SUCCESS";
        case SUPP_BE_FAIL:
                return "FAIL";
        case SUPP_BE_TIMEOUT:
                return "TIMEOUT";
        case SUPP_BE_IDLE:
                return "IDLE";
        case SUPP_BE_INITIALIZE:
                return "INITIALIZE";
        case SUPP_BE_RECEIVE:
                return "RECEIVE";
        default:
                return "UNKNOWN";
        }
}


static const char * eapol_port_status(PortStatus status)
{
        if (status == Authorized)
                return "Authorized";
        else
                return "Unauthorized";
}
#endif /* CONFIG_CTRL_IFACE */


#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
static const char * eapol_port_control(PortControl ctrl)
{
        switch (ctrl) {
        case Auto:
                return "Auto";
        case ForceUnauthorized:
                return "ForceUnauthorized";
        case ForceAuthorized:
                return "ForceAuthorized";
        default:
                return "Unknown";
        }
}
#endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */


/**
 * eapol_sm_configure - Set EAPOL variables
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @heldPeriod: dot1xSuppHeldPeriod
 * @authPeriod: dot1xSuppAuthPeriod
 * @startPeriod: dot1xSuppStartPeriod
 * @maxStart: dot1xSuppMaxStart
 *
 * Set configurable EAPOL state machine variables. Each variable can be set to
 * the given value or ignored if set to -1 (to set only some of the variables).
 */
void eapol_sm_configure(struct eapol_sm *sm, int heldPeriod, int authPeriod,
                        int startPeriod, int maxStart)
{
        if (sm == NULL)
                return;
        if (heldPeriod >= 0)
                sm->heldPeriod = heldPeriod;
        if (authPeriod >= 0)
                sm->authPeriod = authPeriod;
        if (startPeriod >= 0)
                sm->startPeriod = startPeriod;
        if (maxStart >= 0)
                sm->maxStart = maxStart;
}


#ifdef CONFIG_CTRL_IFACE
/**
 * eapol_sm_get_status - Get EAPOL state machine status
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @buf: Buffer for status information
 * @buflen: Maximum buffer length
 * @verbose: Whether to include verbose status information
 * Returns: Number of bytes written to buf.
 *
 * Query EAPOL state machine for status information. This function fills in a
 * text area with current status information from the EAPOL state machine. If
 * the buffer (buf) is not large enough, status information will be truncated
 * to fit the buffer.
 */
int eapol_sm_get_status(struct eapol_sm *sm, char *buf, size_t buflen,
                        int verbose)
{
        int len, ret;
        if (sm == NULL)
                return 0;

        len = os_snprintf(buf, buflen,
                          "Supplicant PAE state=%s\n"
                          "suppPortStatus=%s\n",
                          eapol_supp_pae_state(sm->SUPP_PAE_state),
                          eapol_port_status(sm->suppPortStatus));
        if (len < 0 || (size_t) len >= buflen)
                return 0;

        if (verbose) {
                ret = os_snprintf(buf + len, buflen - len,
                                  "heldPeriod=%u\n"
                                  "authPeriod=%u\n"
                                  "startPeriod=%u\n"
                                  "maxStart=%u\n"
                                  "portControl=%s\n"
                                  "Supplicant Backend state=%s\n",
                                  sm->heldPeriod,
                                  sm->authPeriod,
                                  sm->startPeriod,
                                  sm->maxStart,
                                  eapol_port_control(sm->portControl),
                                  eapol_supp_be_state(sm->SUPP_BE_state));
                if (ret < 0 || (size_t) ret >= buflen - len)
                        return len;
                len += ret;
        }

        len += eap_sm_get_status(sm->eap, buf + len, buflen - len, verbose);

        return len;
}


/**
 * eapol_sm_get_mib - Get EAPOL state machine MIBs
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @buf: Buffer for MIB information
 * @buflen: Maximum buffer length
 * Returns: Number of bytes written to buf.
 *
 * Query EAPOL state machine for MIB information. This function fills in a
 * text area with current MIB information from the EAPOL state machine. If
 * the buffer (buf) is not large enough, MIB information will be truncated to
 * fit the buffer.
 */
int eapol_sm_get_mib(struct eapol_sm *sm, char *buf, size_t buflen)
{
        size_t len;
        int ret;

        if (sm == NULL)
                return 0;
        ret = os_snprintf(buf, buflen,
                          "dot1xSuppPaeState=%d\n"
                          "dot1xSuppHeldPeriod=%u\n"
                          "dot1xSuppAuthPeriod=%u\n"
                          "dot1xSuppStartPeriod=%u\n"
                          "dot1xSuppMaxStart=%u\n"
                          "dot1xSuppSuppControlledPortStatus=%s\n"
                          "dot1xSuppBackendPaeState=%d\n",
                          sm->SUPP_PAE_state,
                          sm->heldPeriod,
                          sm->authPeriod,
                          sm->startPeriod,
                          sm->maxStart,
                          sm->suppPortStatus == Authorized ?
                          "Authorized" : "Unauthorized",
                          sm->SUPP_BE_state);

        if (ret < 0 || (size_t) ret >= buflen)
                return 0;
        len = ret;

        ret = os_snprintf(buf + len, buflen - len,
                          "dot1xSuppEapolFramesRx=%u\n"
                          "dot1xSuppEapolFramesTx=%u\n"
                          "dot1xSuppEapolStartFramesTx=%u\n"
                          "dot1xSuppEapolLogoffFramesTx=%u\n"
                          "dot1xSuppEapolRespFramesTx=%u\n"
                          "dot1xSuppEapolReqIdFramesRx=%u\n"
                          "dot1xSuppEapolReqFramesRx=%u\n"
                          "dot1xSuppInvalidEapolFramesRx=%u\n"
                          "dot1xSuppEapLengthErrorFramesRx=%u\n"
                          "dot1xSuppLastEapolFrameVersion=%u\n"
                          "dot1xSuppLastEapolFrameSource=" MACSTR "\n",
                          sm->dot1xSuppEapolFramesRx,
                          sm->dot1xSuppEapolFramesTx,
                          sm->dot1xSuppEapolStartFramesTx,
                          sm->dot1xSuppEapolLogoffFramesTx,
                          sm->dot1xSuppEapolRespFramesTx,
                          sm->dot1xSuppEapolReqIdFramesRx,
                          sm->dot1xSuppEapolReqFramesRx,
                          sm->dot1xSuppInvalidEapolFramesRx,
                          sm->dot1xSuppEapLengthErrorFramesRx,
                          sm->dot1xSuppLastEapolFrameVersion,
                          MAC2STR(sm->dot1xSuppLastEapolFrameSource));

        if (ret < 0 || (size_t) ret >= buflen - len)
                return len;
        len += ret;

        return len;
}
#endif /* CONFIG_CTRL_IFACE */


/**
 * eapol_sm_rx_eapol - Process received EAPOL frames
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @src: Source MAC address of the EAPOL packet
 * @buf: Pointer to the beginning of the EAPOL data (EAPOL header)
 * @len: Length of the EAPOL frame
 * Returns: 1 = EAPOL frame processed, 0 = not for EAPOL state machine,
 * -1 failure
 */
int eapol_determine_timeout_period(struct eapol_sm *sm)
{
   if (sm->eap->eapfailure)
   {
        sm->idleWhile = 180;
        return 180;
   }
   else
        return 0;
}

int eapol_sm_rx_eapol(struct eapol_sm *sm, const u8 *src, const u8 *buf,
                      size_t len)
{
        const struct ieee802_1x_hdr *hdr;
        const struct ieee802_1x_eapol_key *key;
        int data_len;
        int res = 1;
        size_t plen;

        if (sm == NULL)
                return 0;
        sm->dot1xSuppEapolFramesRx++;
        if (len < sizeof(*hdr)) {
                sm->dot1xSuppInvalidEapolFramesRx++;
                return 0;
        }
        hdr = (const struct ieee802_1x_hdr *) buf;
        sm->dot1xSuppLastEapolFrameVersion = hdr->version;
        os_memcpy(sm->dot1xSuppLastEapolFrameSource, src, ETH_ALEN);
        if (hdr->version < EAPOL_VERSION) {
                /* TODO: backwards compatibility */
        }
        plen = be_to_host16(hdr->length);
        if (plen > len - sizeof(*hdr)) {
                sm->dot1xSuppEapLengthErrorFramesRx++;
                return 0;
        }
#ifdef CONFIG_WPS
        if (sm->conf.workaround &&
            plen < len - sizeof(*hdr) &&
            hdr->type == IEEE802_1X_TYPE_EAP_PACKET &&
            len - sizeof(*hdr) > sizeof(struct eap_hdr)) {
                const struct eap_hdr *ehdr =
                        (const struct eap_hdr *) (hdr + 1);
                u16 elen;

                elen = be_to_host16(ehdr->length);
                if (elen > plen && elen <= len - sizeof(*hdr)) {
                        /*
                         * Buffalo WHR-G125 Ver.1.47 seems to send EAP-WPS
                         * packets with too short EAPOL header length field
                         * (14 octets). This is fixed in firmware Ver.1.49.
                         * As a workaround, fix the EAPOL header based on the
                         * correct length in the EAP packet.
                         */
                        wpa_printf(MSG_DEBUG, "EAPOL: Workaround - fix EAPOL "
                                   "payload length based on EAP header: "
                                   "%d -> %d", (int) plen, elen);
                        plen = elen;
                }
        }
#endif /* CONFIG_WPS */
        data_len = plen + sizeof(*hdr);

        switch (hdr->type) {
        case IEEE802_1X_TYPE_EAP_PACKET:
                if (sm->cached_pmk) {
                        /* Trying to use PMKSA caching, but Authenticator did
                         * not seem to have a matching entry. Need to restart
                         * EAPOL state machines.
                         */
                        eapol_sm_abort_cached(sm);
                }
                wpabuf_free(sm->eapReqData);
                sm->eapReqData = wpabuf_alloc_copy(hdr + 1, plen);
                if (sm->eapReqData) {
                        wpa_printf(MSG_DEBUG, "EAPOL: Received EAP-Packet "
                                   "frame");
                        sm->eapolEap = TRUE;
                        eapol_sm_step(sm);
                }
                break;
        case IEEE802_1X_TYPE_EAPOL_KEY:
                if (plen < sizeof(*key)) {
                        wpa_printf(MSG_DEBUG, "EAPOL: Too short EAPOL-Key "
                                   "frame received");
                        break;
                }
                key = (const struct ieee802_1x_eapol_key *) (hdr + 1);
                if (key->type == EAPOL_KEY_TYPE_WPA ||
                    key->type == EAPOL_KEY_TYPE_RSN) {
                        /* WPA Supplicant takes care of this frame. */
                        wpa_printf(MSG_DEBUG, "EAPOL: Ignoring WPA EAPOL-Key "
                                   "frame in EAPOL state machines");
                        res = 0;
                        break;
                }
                if (key->type != EAPOL_KEY_TYPE_RC4) {
                        wpa_printf(MSG_DEBUG, "EAPOL: Ignored unknown "
                                   "EAPOL-Key type %d", key->type);
                        break;
                }
                os_free(sm->last_rx_key);
                sm->last_rx_key = os_malloc(data_len);
                if (sm->last_rx_key) {
                        wpa_printf(MSG_DEBUG, "EAPOL: Received EAPOL-Key "
                                   "frame");
                        os_memcpy(sm->last_rx_key, buf, data_len);
                        sm->last_rx_key_len = data_len;
                        sm->rxKey = TRUE;
                        eapol_sm_step(sm);
                }
                break;
        default:
                wpa_printf(MSG_DEBUG, "EAPOL: Received unknown EAPOL type %d",
                           hdr->type);
                sm->dot1xSuppInvalidEapolFramesRx++;
                break;
        }

        return res;
}


/**
 * eapol_sm_notify_tx_eapol_key - Notification about transmitted EAPOL packet
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * Notify EAPOL state machine about transmitted EAPOL packet from an external
 * component, e.g., WPA. This will update the statistics.
 */
void eapol_sm_notify_tx_eapol_key(struct eapol_sm *sm)
{
        if (sm)
                sm->dot1xSuppEapolFramesTx++;
}

void wpa_sm_reset_timeout(struct eapol_sm *sm)
{
        if (sm->eap->eapfailure)
                sm->eap->eapfailure = 0;
}

/**
 * eapol_sm_notify_portEnabled - Notification about portEnabled change
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @enabled: New portEnabled value
 *
 * Notify EAPOL state machine about new portEnabled value.
 */
void eapol_sm_notify_portEnabled(struct eapol_sm *sm, Boolean enabled)
{
        if (sm == NULL)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
                   "portEnabled=%d", enabled);
        sm->portEnabled = enabled;
        eapol_sm_step(sm);
}


/**
 * eapol_sm_notify_portValid - Notification about portValid change
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @valid: New portValid value
 *
 * Notify EAPOL state machine about new portValid value.
 */
void eapol_sm_notify_portValid(struct eapol_sm *sm, Boolean valid)
{
        if (sm == NULL)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
                   "portValid=%d", valid);
        sm->portValid = valid;
        eapol_sm_step(sm);
}


/**
 * eapol_sm_notify_eap_success - Notification of external EAP success trigger
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @success: %TRUE = set success, %FALSE = clear success
 *
 * Notify the EAPOL state machine that external event has forced EAP state to
 * success (success = %TRUE). This can be cleared by setting success = %FALSE.
 *
 * This function is called to update EAP state when WPA-PSK key handshake has
 * been completed successfully since WPA-PSK does not use EAP state machine.
 */
void eapol_sm_notify_eap_success(struct eapol_sm *sm, Boolean success)
{
        if (sm == NULL)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
                   "EAP success=%d", success);
        sm->eapSuccess = success;
        sm->altAccept = success;
        if (success)
                eap_notify_success(sm->eap);
        eapol_sm_step(sm);
}


/**
 * eapol_sm_notify_eap_fail - Notification of external EAP failure trigger
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @fail: %TRUE = set failure, %FALSE = clear failure
 *
 * Notify EAPOL state machine that external event has forced EAP state to
 * failure (fail = %TRUE). This can be cleared by setting fail = %FALSE.
 */
void eapol_sm_notify_eap_fail(struct eapol_sm *sm, Boolean fail)
{
        if (sm == NULL)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
                   "EAP fail=%d", fail);
        sm->eapFail = fail;
        sm->altReject = fail;
        eapol_sm_step(sm);
}


/**
 * eapol_sm_notify_config - Notification of EAPOL configuration change
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @config: Pointer to current network EAP configuration
 * @conf: Pointer to EAPOL configuration data
 *
 * Notify EAPOL state machine that configuration has changed. config will be
 * stored as a backpointer to network configuration. This can be %NULL to clear
 * the stored pointed. conf will be copied to local EAPOL/EAP configuration
 * data. If conf is %NULL, this part of the configuration change will be
 * skipped.
 */
void eapol_sm_notify_config(struct eapol_sm *sm,
                            struct eap_peer_config *config,
                            const struct eapol_config *conf)
{
        if (sm == NULL)
                return;

        sm->config = config;

        if (conf == NULL)
                return;

        sm->conf.accept_802_1x_keys = conf->accept_802_1x_keys;
        sm->conf.required_keys = conf->required_keys;
        sm->conf.fast_reauth = conf->fast_reauth;
        sm->conf.workaround = conf->workaround;
        if (sm->eap) {
                eap_set_fast_reauth(sm->eap, conf->fast_reauth);
                eap_set_workaround(sm->eap, conf->workaround);
                eap_set_force_disabled(sm->eap, conf->eap_disabled);
        }
}


/**
 * eapol_sm_get_key - Get master session key (MSK) from EAP
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @key: Pointer for key buffer
 * @len: Number of bytes to copy to key
 * Returns: 0 on success (len of key available), maximum available key len
 * (>0) if key is available but it is shorter than len, or -1 on failure.
 *
 * Fetch EAP keying material (MSK, eapKeyData) from EAP state machine. The key
 * is available only after a successful authentication.
 */
int eapol_sm_get_key(struct eapol_sm *sm, u8 *key, size_t len)
{
        const u8 *eap_key;
        size_t eap_len;

        if (sm == NULL || !eap_key_available(sm->eap)) {
                wpa_printf(MSG_DEBUG, "EAPOL: EAP key not available");
                return -1;
        }
        eap_key = eap_get_eapKeyData(sm->eap, &eap_len);
        if (eap_key == NULL) {
                wpa_printf(MSG_DEBUG, "EAPOL: Failed to get eapKeyData");
                return -1;
        }
        if (len > eap_len) {
                wpa_printf(MSG_DEBUG, "EAPOL: Requested key length (%lu) not "
                           "available (len=%lu)",
                           (unsigned long) len, (unsigned long) eap_len);
                return eap_len;
        }
        os_memcpy(key, eap_key, len);
        wpa_printf(MSG_DEBUG, "EAPOL: Successfully fetched key (len=%lu)",
                   (unsigned long) len);
        return 0;
}


/**
 * eapol_sm_notify_logoff - Notification of logon/logoff commands
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @logoff: Whether command was logoff
 *
 * Notify EAPOL state machines that user requested logon/logoff.
 */
void eapol_sm_notify_logoff(struct eapol_sm *sm, Boolean logoff)
{
        if (sm) {
                sm->userLogoff = logoff;
                eapol_sm_step(sm);
        }
}


/**
 * eapol_sm_notify_pmkid_attempt - Notification of successful PMKSA caching
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * Notify EAPOL state machines that PMKSA caching was successful. This is used
 * to move EAPOL and EAP state machines into authenticated/successful state.
 */
void eapol_sm_notify_cached(struct eapol_sm *sm)
{
        if (sm == NULL)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: PMKSA caching was used - skip EAPOL");
        sm->SUPP_PAE_state = SUPP_PAE_AUTHENTICATED;
        sm->suppPortStatus = Authorized;
        sm->portValid = TRUE;
        eap_notify_success(sm->eap);
        eapol_sm_step(sm);
}


/**
 * eapol_sm_notify_pmkid_attempt - Notification of PMKSA caching
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @attempt: Whether PMKSA caching is tried
 *
 * Notify EAPOL state machines whether PMKSA caching is used.
 */
void eapol_sm_notify_pmkid_attempt(struct eapol_sm *sm, int attempt)
{
        if (sm == NULL)
                return;
        if (attempt) {
                wpa_printf(MSG_DEBUG, "RSN: Trying to use cached PMKSA");
                sm->cached_pmk = TRUE;
        } else {
                wpa_printf(MSG_DEBUG, "RSN: Do not try to use cached PMKSA");
                sm->cached_pmk = FALSE;
        }
}


static void eapol_sm_abort_cached(struct eapol_sm *sm)
{
        wpa_printf(MSG_DEBUG, "RSN: Authenticator did not accept PMKID, "
                   "doing full EAP authentication");
        if (sm == NULL)
                return;
        sm->cached_pmk = FALSE;
        sm->SUPP_PAE_state = SUPP_PAE_CONNECTING;
        sm->suppPortStatus = Unauthorized;

        /* Make sure we do not start sending EAPOL-Start frames first, but
         * instead move to RESTART state to start EAPOL authentication. */
        sm->startWhen = 3;
        eapol_enable_timer_tick(sm);

        if (sm->ctx->aborted_cached)
                sm->ctx->aborted_cached(sm->ctx->ctx);
}


/**
 * eapol_sm_register_scard_ctx - Notification of smart card context
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @ctx: Context data for smart card operations
 *
 * Notify EAPOL state machines of context data for smart card operations. This
 * context data will be used as a parameter for scard_*() functions.
 */
void eapol_sm_register_scard_ctx(struct eapol_sm *sm, void *ctx)
{
        if (sm) {
                sm->ctx->scard_ctx = ctx;
                eap_register_scard_ctx(sm->eap, ctx);
        }
}


/**
 * eapol_sm_notify_portControl - Notification of portControl changes
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @portControl: New value for portControl variable
 *
 * Notify EAPOL state machines that portControl variable has changed.
 */
void eapol_sm_notify_portControl(struct eapol_sm *sm, PortControl portControl)
{
        if (sm == NULL)
                return;
        wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
                   "portControl=%s", eapol_port_control(portControl));
        sm->portControl = portControl;
        eapol_sm_step(sm);
}


/**
 * eapol_sm_notify_ctrl_attached - Notification of attached monitor
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * Notify EAPOL state machines that a monitor was attached to the control
 * interface to trigger re-sending of pending requests for user input.
 */
void eapol_sm_notify_ctrl_attached(struct eapol_sm *sm)
{
        if (sm == NULL)
                return;
        eap_sm_notify_ctrl_attached(sm->eap);
}


/**
 * eapol_sm_notify_ctrl_response - Notification of received user input
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * Notify EAPOL state machines that a control response, i.e., user
 * input, was received in order to trigger retrying of a pending EAP request.
 */
void eapol_sm_notify_ctrl_response(struct eapol_sm *sm)
{
        if (sm == NULL)
                return;
        if (sm->eapReqData && !sm->eapReq) {
                wpa_printf(MSG_DEBUG, "EAPOL: received control response (user "
                           "input) notification - retrying pending EAP "
                           "Request");
                sm->eapolEap = TRUE;
                sm->eapReq = TRUE;
                eapol_sm_step(sm);
        }
}


/**
 * eapol_sm_request_reauth - Request reauthentication
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * This function can be used to request EAPOL reauthentication, e.g., when the
 * current PMKSA entry is nearing expiration.
 */
void eapol_sm_request_reauth(struct eapol_sm *sm)
{
        if (sm == NULL || sm->SUPP_PAE_state != SUPP_PAE_AUTHENTICATED)
                return;
        eapol_sm_txStart(sm);
}


/**
 * eapol_sm_notify_lower_layer_success - Notification of lower layer success
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 * @in_eapol_sm: Whether the caller is already running inside EAPOL state
 * machine loop (eapol_sm_step())
 *
 * Notify EAPOL (and EAP) state machines that a lower layer has detected a
 * successful authentication. This is used to recover from dropped EAP-Success
 * messages.
 */
void eapol_sm_notify_lower_layer_success(struct eapol_sm *sm, int in_eapol_sm)
{
        if (sm == NULL)
                return;
        eap_notify_lower_layer_success(sm->eap);
        if (!in_eapol_sm)
                eapol_sm_step(sm);
}


/**
 * eapol_sm_invalidate_cached_session - Mark cached EAP session data invalid
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 */
void eapol_sm_invalidate_cached_session(struct eapol_sm *sm)
{
        if (sm)
                eap_invalidate_cached_session(sm->eap);
}


static struct eap_peer_config * eapol_sm_get_config(void *ctx)
{
        struct eapol_sm *sm = ctx;
        return sm ? sm->config : NULL;
}


static struct wpabuf * eapol_sm_get_eapReqData(void *ctx)
{
        struct eapol_sm *sm = ctx;
        if (sm == NULL || sm->eapReqData == NULL)
                return NULL;

        return sm->eapReqData;
}


static Boolean eapol_sm_get_bool(void *ctx, enum eapol_bool_var variable)
{
        struct eapol_sm *sm = ctx;
        if (sm == NULL)
                return FALSE;
        switch (variable) {
        case EAPOL_eapSuccess:
                return sm->eapSuccess;
        case EAPOL_eapRestart:
                return sm->eapRestart;
        case EAPOL_eapFail:
                return sm->eapFail;
        case EAPOL_eapResp:
                return sm->eapResp;
        case EAPOL_eapNoResp:
                return sm->eapNoResp;
        case EAPOL_eapReq:
                return sm->eapReq;
        case EAPOL_portEnabled:
                return sm->portEnabled;
        case EAPOL_altAccept:
                return sm->altAccept;
        case EAPOL_altReject:
                return sm->altReject;
        }
        return FALSE;
}


static void eapol_sm_set_bool(void *ctx, enum eapol_bool_var variable,
                              Boolean value)
{
        struct eapol_sm *sm = ctx;
        if (sm == NULL)
                return;
        switch (variable) {
        case EAPOL_eapSuccess:
                sm->eapSuccess = value;
                break;
        case EAPOL_eapRestart:
                sm->eapRestart = value;
                break;
        case EAPOL_eapFail:
                sm->eapFail = value;
                break;
        case EAPOL_eapResp:
                sm->eapResp = value;
                break;
        case EAPOL_eapNoResp:
                sm->eapNoResp = value;
                break;
        case EAPOL_eapReq:
                sm->eapReq = value;
                break;
        case EAPOL_portEnabled:
                sm->portEnabled = value;
                break;
        case EAPOL_altAccept:
                sm->altAccept = value;
                break;
        case EAPOL_altReject:
                sm->altReject = value;
                break;
        }
}


static unsigned int eapol_sm_get_int(void *ctx, enum eapol_int_var variable)
{
        struct eapol_sm *sm = ctx;
        if (sm == NULL)
                return 0;
        switch (variable) {
        case EAPOL_idleWhile:
                return sm->idleWhile;
        }
        return 0;
}


static void eapol_sm_set_int(void *ctx, enum eapol_int_var variable,
                             unsigned int value)
{
        struct eapol_sm *sm = ctx;
        if (sm == NULL)
                return;
        switch (variable) {
        case EAPOL_idleWhile:
                sm->idleWhile = value;
                eapol_enable_timer_tick(sm);
                break;
        }
}


static void eapol_sm_set_config_blob(void *ctx, struct wpa_config_blob *blob)
{
#ifndef CONFIG_NO_CONFIG_BLOBS
        struct eapol_sm *sm = ctx;
        if (sm && sm->ctx && sm->ctx->set_config_blob)
                sm->ctx->set_config_blob(sm->ctx->ctx, blob);
#endif /* CONFIG_NO_CONFIG_BLOBS */
}


static const struct wpa_config_blob *
eapol_sm_get_config_blob(void *ctx, const char *name)
{
#ifndef CONFIG_NO_CONFIG_BLOBS
        struct eapol_sm *sm = ctx;
        if (sm && sm->ctx && sm->ctx->get_config_blob)
                return sm->ctx->get_config_blob(sm->ctx->ctx, name);
        else
                return NULL;
#else /* CONFIG_NO_CONFIG_BLOBS */
        return NULL;
#endif /* CONFIG_NO_CONFIG_BLOBS */
}


static void eapol_sm_notify_pending(void *ctx)
{
        struct eapol_sm *sm = ctx;
        if (sm == NULL)
                return;
        if (sm->eapReqData && !sm->eapReq) {
                wpa_printf(MSG_DEBUG, "EAPOL: received notification from EAP "
                           "state machine - retrying pending EAP Request");
                sm->eapolEap = TRUE;
                sm->eapReq = TRUE;
                eapol_sm_step(sm);
        }
}


#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
static void eapol_sm_eap_param_needed(void *ctx, const char *field,
                                      const char *txt)
{
        struct eapol_sm *sm = ctx;
        wpa_printf(MSG_DEBUG, "EAPOL: EAP parameter needed");
        if (sm->ctx->eap_param_needed)
                sm->ctx->eap_param_needed(sm->ctx->ctx, field, txt);
}
#else /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */
#define eapol_sm_eap_param_needed NULL
#endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */


static struct eapol_callbacks eapol_cb =
{
        eapol_sm_get_config,
        eapol_sm_get_bool,
        eapol_sm_set_bool,
        eapol_sm_get_int,
        eapol_sm_set_int,
        eapol_sm_get_eapReqData,
        eapol_sm_set_config_blob,
        eapol_sm_get_config_blob,
        eapol_sm_notify_pending,
        eapol_sm_eap_param_needed
};


/**
 * eapol_sm_init - Initialize EAPOL state machine
 * @ctx: Pointer to EAPOL context data; this needs to be an allocated buffer
 * and EAPOL state machine will free it in eapol_sm_deinit()
 * Returns: Pointer to the allocated EAPOL state machine or %NULL on failure
 *
 * Allocate and initialize an EAPOL state machine.
 */
struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx)
{
        struct eapol_sm *sm;
        struct eap_config conf;
        sm = os_zalloc(sizeof(*sm));
        if (sm == NULL)
                return NULL;
        sm->ctx = ctx;

        sm->portControl = Auto;

        /* Supplicant PAE state machine */
        sm->heldPeriod = 60;
        sm->startPeriod = 30;
        sm->maxStart = 3;

        /* Supplicant Backend state machine */
        sm->authPeriod = 30;

        os_memset(&conf, 0, sizeof(conf));
#ifdef EAP_TLS_OPENSSL
        conf.opensc_engine_path = ctx->opensc_engine_path;
        conf.pkcs11_engine_path = ctx->pkcs11_engine_path;
        conf.pkcs11_module_path = ctx->pkcs11_module_path;
#endif /* EAP_TLS_OPENSSL */
        conf.wps = ctx->wps;

        sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf);
        if (sm->eap == NULL) {
                os_free(sm);
                return NULL;
        }

        /* Initialize EAPOL state machines */
        sm->initialize = TRUE;
        eapol_sm_step(sm);
        sm->initialize = FALSE;
        eapol_sm_step(sm);

        sm->timer_tick_enabled = 1;
        eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);

        return sm;
}


/**
 * eapol_sm_deinit - Deinitialize EAPOL state machine
 * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
 *
 * Deinitialize and free EAPOL state machine.
 */
void eapol_sm_deinit(struct eapol_sm *sm)
{
        if (sm == NULL)
                return;
        eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
        eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm);
        eap_peer_sm_deinit(sm->eap);
        os_free(sm->last_rx_key);
        wpabuf_free(sm->eapReqData);
        os_free(sm->ctx);
        os_free(sm);
}
